﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NTFS
{
    public class SistemaNTFS
    {
        #region Construtor
        public SistemaNTFS()
        {
            indice = new Indice();
        }
        #endregion
        #region Atributos
        Indice indice;
        #endregion
        #region Métodos
        public void Inicia()
        {
            if (!File.Exists(Constantes.ARQ))
            {
                //File.Delete(Constantes.ARQ);
                //File.CreateText("file.txt");
                // $ - metadados de sistema
                // ~ - pasta
                //000 - cluster inicial toda linha tem esse id
                //[ - Inicia arquivo gravado
                //> - Indica metadata de um arquivo
                // ~$ - Nome 
                //6 - permissão padrão unix
                //sistema - nome do usuario
                //a - tipo de arquivo
                //0 - id do objeto
                //-1 - id do pai
                //> - Fim da metadata
                //$ - nome do arquivo de sistema
                //mft - nome do arquivo
                //6sistema -permissão e usuário
                //000 - indica o cluster do arquivo referencia para o próprio mft
                //> - inicia nova descrição de arquivo na tabela
                //-------------------------------------------------------------
                string s = @"000[>$~;6sistema,6Publico;003;d;0>$mft;6sistema;002;a;1>$bitmap;6sistema;001;a;2]
001[11110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]
002[IdCont:3,]
003[1,2]";
                for (int i = 4; i < 200; i++)
                {
                    s = s + "\n" + "" + i.ToString("000");
                }
                FileStream fs = new FileStream(Constantes.ARQ, FileMode.Create);
                StreamWriter w = new StreamWriter(fs);
                w.Write(s);
                w.Flush();
                w.Close();
                fs.Close();
                indice.CarregaIndice();
            }
        }

        

        public Mft PegaMftPublic(string s)
        {
            indice.CarregaIndice();
            return indice.PegaMft(s);
        }

        public Mft PegaMftPublic(int id)
        {
            indice.CarregaIndice();
            return indice.PegaMft(id);
        }

        /// <summary>
        /// Retorna o próximo código para um novo objeto.
        /// </summary>
        /// <returns></returns>
        public int ProxId()
        {
            int result;
            result = Convert.ToInt32(PegaValorChaveMFT("IdCont"));
            return result;
        }

        /// <summary>
        /// Pega o valor da chave procurada no arquivo principal
        /// de controle do sistema de arquivo
        /// </summary>
        /// <param name="chave">Chave para retonar o valor</param>
        /// <returns>retorna o valor do arquivo de mft da chave</returns>
        private string PegaValorChaveMFT(string chave)
        {
            indice.CarregaIndice();
            Mft mft = indice.PegaMft("$mft");
            Arquivo arq = Openfile("$mft", "sistema");
            int ini = arq.Conteudo.IndexOf(chave+":")+chave.Length+1;
            if(ini<0)
                throw new Exception("Chave inválida");
            int fim = arq.Conteudo.IndexOf(',',ini);
            string res = arq.Conteudo.Substring(ini, fim - ini);
            return res;
        }
        /// <summary>
        /// Alterar valor da chave de 
        /// </summary>
        /// <param name="chave"></param>
        /// <param name="valor"></param>
        private void AtualizaValorChaveMFT(string chave, string valor)
        {
            indice.CarregaIndice();
            chave += ':';
            Mft mft = indice.PegaMft("$mft");
            Arquivo arq = Openfile("$mft","sistema");
            string res;
            string inicial, fim;
            int ini = arq.Conteudo.IndexOf(chave)+chave.Length;
            inicial = arq.Conteudo.Substring(0, ini);
            int meio = arq.Conteudo.IndexOf(',', ini);
            fim = arq.Conteudo.Substring(meio, arq.Conteudo.Length-meio);
            res = inicial + valor + fim;
            arq.Conteudo = res;
            GravaArquivo(arq);
        }

        /// <summary>
        /// Grava Arquivo.
        /// </summary>
        /// <param name="arq">Arquivo a ser atualizado ou adicionado.</param>        
        public void GravaArquivo(Arquivo arq, int idPasta = 0, string user ="sistema")
        {
            Arquivo dir = Openfile(idPasta, user);
            //Verifica permissão
            PermiteAcesso(dir.Info, user, new int[] { 4, 6 });
                
            //Verifica se o arquivo já possui id.
            if (arq.Info.Id == -1)
            {
                arq.Info.Id = ProxId();
                AtualizaValorChaveMFT("IdCont",(arq.Info.Id+1).ToString());
                GravaPasta(idPasta,user,arq.Info.Id);                
            }
            List<string> res = new List<string>();
            //Pega o número de clusters necessarios.
            int tam = (int)Math.Ceiling((double)arq.Conteudo.Length/(double)Constantes.TAM_CLUSTER);
            if (tam == 0)
                tam = 1;
            //Subtrai os clusters que ja sao do arquivo
            int alocar = tam - arq.Info.Clusters.Count;
            List<int> clusters = new List<int>();
            //Procura cluesters vagos
            if (alocar > 0 && arq.Info.Nome.CompareTo("$bitmap") != 0)
                clusters.AddRange(RetornaPosicoesLivres(alocar));
            //Adiciona clusters já alocados pelo arquivo.
            clusters.AddRange(arq.Info.Clusters);
            arq.Info.Clusters = clusters;
            //Marca clusters.
            if(alocar>0 && arq.Info.Nome.CompareTo("$bitmap")!=0)
                MarcaCluster(clusters.ToArray(),true);
            //Abre o aquivo para leitura.
            StreamReader sr = new StreamReader(Constantes.ARQ);            
            string linha = sr.ReadLine();
            //Percorre todas as linha para modificar os clusters.
            while(linha!=null)
            {
                int cluster=int.Parse(linha.Substring(0,Constantes.TAM_COD_CLUSTER));
                if(clusters.Any<int>(x => x == cluster))
                {
                    linha = String.Format("{0:D3}", cluster);
                    string valor = "";
                    //Subistitui valor pelo novo valor
                    if (arq.Conteudo.Length >= Constantes.TAM_CLUSTER && cluster!=1)
                    {
                        valor = arq.Conteudo.Substring(0, Constantes.TAM_CLUSTER);
                        //Atualiza dados a serem gravados.
                        arq.Conteudo = arq.Conteudo.Substring(valor.Length, arq.Conteudo.Length - valor.Length);
                    }
                    else
                    {
                        //Matém o valor.
                        valor = arq.Conteudo.Substring(0, arq.Conteudo.Length);                        
                    }
                    
                    linha += '[' + valor + "]";
                }
                //Adiciona linha ao arquivo.
                res.Add(linha);
                linha = sr.ReadLine();
            }
            sr.Close();
            //Efetua as atualizações no arquivo.
            File.WriteAllLines(Constantes.ARQ, res.ToArray());
            //Adiciona o arquivo na tabela de arquivos.
            indice.AdicionaMFT(arq.Info);
        }

        private void GravaPasta(int idPasta,string user,int idArq)
        {
            Arquivo dir = Openfile(idPasta,user);
            if (dir.Conteudo != "")
                dir.Conteudo += ',' + idArq.ToString();
            else
                dir.Conteudo = idArq.ToString();
            StreamReader sr = new StreamReader(Constantes.ARQ);
            StringBuilder sb = new StringBuilder("");
            string linha;
            linha = sr.ReadLine();
            while(linha !=null)
            {
                int cluster=int.Parse(linha.Substring(0,Constantes.TAM_COD_CLUSTER));
                if (dir.Info.Clusters.Any<int>(x => x == cluster))
                {
                    sb.Append(string.Format("{0:D3}", cluster) + '[' + dir.Conteudo + "]\n");
                    break;
                }
                else
                    sb.Append(linha+'\n');
                linha = sr.ReadLine();
            }
            sb.Append(sr.ReadToEnd());
            sr.Close();
            StreamWriter sw = new StreamWriter(Constantes.ARQ);
            sw.Write(sb.ToString());
            sw.Close();
        }

        public void DeletaArquivo(Arquivo arq, Arquivo dir, string user="sistema")
        {
            PermiteAcesso(arq.Info,user,new int[]{4,6});
            indice.RemoveMFT(arq.Info);
            int ini, fim, tId;
            ini = dir.Conteudo.IndexOf(arq.Info.Id.ToString() + ',');
            fim = dir.Conteudo.IndexOf(',' + arq.Info.Id.ToString());
            tId = arq.Info.Id.ToString().Length;
            //Remove id do arquivo do diretório.
            if(dir.Conteudo.Contains(','+arq.Info.Id.ToString()+','))
            {
                dir.Conteudo = dir.Conteudo.Replace(',' + arq.Info.Id.ToString() + ',',",");              
            }
            else if (dir.Conteudo.CompareTo(arq.Info.Id.ToString()) == 0)
            {
                dir.Conteudo = "";
            }
            else if (fim == dir.Conteudo.Length-tId-1)
            {
                dir.Conteudo = dir.Conteudo.Substring(0, dir.Conteudo.Length - tId - 1);
            }
            else if (ini==0)
            {
                dir.Conteudo = dir.Conteudo.Replace(arq.Info.Id.ToString()+',',"");
            }
            
            GravaArquivo(dir);
            //dir.Conteudo = dir.Conteudo.Substring(0,)
            MarcaCluster(arq.Info.Clusters.ToArray<int>(), false);
        }
 
        /// <summary>
        /// Marcar cluster alocados
        /// </summary>
        /// <param name="clusters"></param>
        private void MarcaCluster(int[]clusters,bool ocupado)
        {
            Arquivo arq = Openfile("$bitmap", "sistema");
            int tam = arq.Conteudo.Length;
            char livre = ocupado?'1':'0';
            StringBuilder sb = new StringBuilder(arq.Conteudo);
            for (int i = 0; i < tam; i++)
            {
                if(clusters.Any<int>(x=>x==i))
                {
                    sb[i] = livre;
                }
            }
            arq.Conteudo = sb.ToString();
            GravaArquivo(arq);
        }

        /// <summary>
        /// Percorre o bitmap para capturar blocos vazios.
        /// </summary>
        /// <param name="qtd">Quantidade de blocos para alocacao</param>
        /// <returns>Retorna clusters liberados</returns>
        private int[] RetornaPosicoesLivres(int qtd)
        {
            int[] clusters = new int[qtd];
            int cont =0;
            Arquivo arq = Openfile("$bitmap", "sistema");
            for (int i = 0; i < arq.Conteudo.Length && cont<qtd; i++)
            {
                if(arq.Conteudo[i]=='0')
                    clusters[cont++] = i;
            }
            return clusters;
        }
        
        private void PermiteAcesso(Mft mft, string user, int[] permissao)
        {
            if (mft.Users.Exists(x => x.nome.CompareTo("Publico") == 0))
                return;
            foreach (user u in mft.Users)
            {
                if (u.nome == user)
                {   
                    for (int i = 0; i < permissao.Length; i++)
		            {
                        if(u.permissoes == permissao[i])
                            return;			 
		            }                    
                }
            }
            throw new AcessoNegadoException();
        }

        public bool PermiteAcessoLeitura(int idArq,string user)
        {
            try
            {
                Mft mft = PegaMftPublic(idArq);
                int[] per = new int[] { 2,6 };
                PermiteAcesso(mft,user,per);
                return true;
            }
            catch { return false; }
        }

        /// <summary>
        /// Retorna Arquivo pelo nome.
        /// </summary>
        /// <param name="nome"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        public Arquivo Openfile(string nome, string user)
        {
            Arquivo a;
            a = new Arquivo();
            Mft info = indice.PegaMft(nome);
            int[] permissoes = {6,2};
            PermiteAcesso(info, user,permissoes);

            foreach (int i in info.Clusters)
            {
                string leitor;
                leitor = File.ReadLines(Constantes.ARQ).Skip(i).Take(1).First();
                leitor = leitor.Remove(leitor.IndexOf(']'));
                leitor = leitor.Remove(leitor.IndexOf(i.ToString("000")), 4);
                a.Conteudo = a.Conteudo + leitor;
            }
            a.Info = info;
            return a;
        }

        /// <summary>
        /// Abre arquivo pelo id.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        public Arquivo Openfile(int id, string user)
        {
            Arquivo a;
            a = new Arquivo();
            Mft info = indice.PegaMft(id);
            int[] permissoes = { 6, 2 };
            PermiteAcesso(info, user, permissoes);

            foreach (int i in info.Clusters)
            {
                string leitor;
                leitor = File.ReadLines(Constantes.ARQ).Skip(i).Take(1).First();
                leitor = leitor.Remove(leitor.IndexOf(']'));
                leitor = leitor.Remove(leitor.IndexOf(i.ToString("000")), 4);
                a.Conteudo = a.Conteudo + leitor;
            }
            a.Info = info;
            return a;
        }
        #endregion


        internal void Deleta()
        {
            File.Delete(Constantes.ARQ);
        }
    }
}
